//
//  IOTCGame.m
//  2048-cleanModel
//
//  Created by User-16 on 4/8/14.
//  Copyright (c) 2014 IOTrainingCamp. All rights reserved.
//

#import "IOTCGame.h"
#import "NSIndexPath+additive.h"
#import "config.h"

//TODO: implement size setter to filter whole numbers only

@interface IOTCGame ()
{
    unsigned int** _tiles;
    //for each tile says whether it's the result of addition
    bool** _isResultOfAddition;
}

@property bool hasMoved;
@property NSMutableArray* emptyTiles;
@property (readwrite) unsigned long score;

@end

@implementation IOTCGame

#pragma mark - Singleton
static BOOL isInited = NO;

//returns the only instance of this class
+(id)sharedInstance
{
    static IOTCGame* instance = nil;
    if ( !instance ) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[super alloc] init];
        });
        
    }
    return instance;
}

+(id)alloc {
    return [IOTCGame sharedInstance];
}

-(id)copy {
    return [IOTCGame sharedInstance];
}
-(id)mutableCopy {
    return [IOTCGame sharedInstance];
}

- (id)init
{
    if (!isInited) {
        self = [super init];
        if (self) {
            
        }
        isInited = YES;
    }
    return self;
}


-(void) loadANewGameWithSize:(CGPoint) size
{
    self.size = size;
    self.score = 0;
    
    //initialize the tile arrays
    if(_tiles)
    {
        for(int row = 0; row < self.size.x; row++)
        {
            free(_tiles[row]);
        }
        free(_tiles);

    }
    if(_isResultOfAddition)
    {
        for(int row = 0; row < self.size.x; row++)
        {
            free(_isResultOfAddition[row]);
        }
            free(_isResultOfAddition);
    }
    _tiles = (unsigned int **)malloc(sizeof(unsigned int*)*self.size.x);
    _isResultOfAddition = (bool **)malloc(sizeof(bool*)*self.size.x);
    self.emptyTiles = [NSMutableArray array];
    
    for(int row = 0; row < self.size.x; row++)
    {
        _tiles[row] = (unsigned int *)malloc(sizeof(unsigned int)*self.size.y);
        _isResultOfAddition[row] = (bool*) malloc(sizeof(bool)*self.size.y);
        for(int col = 0; col < self.size.y; col++)
        {
            _tiles[row][col] = 0;
            [self.delegate updateTileAtIndex:[NSIndexPath indexPathForRow:row inSection:col]];
            [self.emptyTiles addObject:[NSIndexPath indexPathForRow:row inSection:col]];
        }
    }
    
    self.hasMoved = NO;
    [self generateNewTile];
    [self generateNewTile];

}

-(NumberView*) numberViewForIndexPath:(NSIndexPath*) index
{
    if([self indexPathIsOutsideBounds:index]){
        NSLog(@"requested view outside the game borders!");
        return nil;
    }
    return [[NumberView alloc] initWithNumber:_tiles[index.row][index.section] ];
}

-(void) moveInDirection:(MoveDirection) direction
{
    
    //reset the movement information
    self.hasMoved = NO;
    for(int row = 0; row < self.size.x; row++)
    {
        for(int col = 0; col < self.size.y; col++)
        {
            _isResultOfAddition[row][col] = NO;
        }
    }

    // depending on the direction set the order for moving tiles
    switch (direction) {
        case DOWN:
            for(int col = 0; col<= self.size.x-1; col++)
            {
                for(int row = self.size.y - 2; row>= 0; row--)
                {
                    [self moveTileAtIndex:[NSIndexPath indexPathForRow:row inSection:col] inDirection:direction];
                }
            }
            break;

        case LEFT:
            for(int row = self.size.y - 1; row>= 0; row--)
            {
                for(int col = 1; col<= self.size.x-1; col++)
                {
                    [self moveTileAtIndex:[NSIndexPath indexPathForRow:row inSection:col] inDirection:direction];
                }
            }
            break;
        case RIGHT:
            for(int row = self.size.y - 1; row>= 0; row--)
            {
                for(int col = self.size.x-2; col>=0 ; col--)
                {
                    [self moveTileAtIndex:[NSIndexPath indexPathForRow:row inSection:col] inDirection:direction];
                }
            }

            break;
        case UP:
            for(int col = 0; col<= self.size.x-1; col++)
            {
                for(int row = 1; row<= self.size.y - 1; row++)
                {
                    [self moveTileAtIndex:[NSIndexPath indexPathForRow:row inSection:col] inDirection:direction];
                }
            }
            break;
        default:
            break;
    }
    
    if(self.hasMoved) [self generateNewTile];
    [self printTiles];
}

-(void) updateBoard
{
    for(int row = 0; row < self.size.x; row++)
    {
        for(int col = 0; col < self.size.y; col++)
        {
            [self.delegate updateTileAtIndex:[NSIndexPath indexPathForRow:row inSection:col]];
        }
    }
    
}

#pragma  mark - private methods

//TODO: make it a separate class
-(void) moveTileAtIndex:(NSIndexPath*) index inDirection:(MoveDirection) direction;
{
    //don't move empty tiles
    unsigned int currentNumber = _tiles[index.row][index.section];
    if(currentNumber == 0) return;
    
    //variables for the direction of movement and the index to move to
    NSIndexPath* increment;
    NSIndexPath* pathToMoveTo;
    
    switch (direction) {
        case DOWN:
            if(index.row == self.size.y - 1) return;
            increment = [NSIndexPath indexPathForRow: 1 inSection:0];
            break;
            
        case LEFT:
            if(index.section== 0) return;
            increment = [NSIndexPath indexPathForRow:0 inSection:-1];
            break;
            
        case RIGHT:
            if(index.section == self.size.x - 1) return;
            increment = [NSIndexPath indexPathForRow:0  inSection: 1];
            break;
            
        case UP:
            if(index.row == 0) return;
            increment = [NSIndexPath indexPathForRow: - 1  inSection:0];
            break;
        default:
            NSLog(@"Not a valid Moving Direction!");
            break;
    }
    
    //move along the empty tiles
    pathToMoveTo = [index addIndexPath:increment];
    if([self indexPathIsOutsideBounds:pathToMoveTo]) return;
    
    while (_tiles[pathToMoveTo.row][pathToMoveTo.section] == 0) {
        pathToMoveTo = [pathToMoveTo addIndexPath:increment];
        if([self indexPathIsOutsideBounds:pathToMoveTo])
        {
            pathToMoveTo = [pathToMoveTo subtractIndexPath:increment];
            break;
        }
    }
    
    //add to the nonempty tile if possible
    if(_tiles[pathToMoveTo.row][pathToMoveTo.section] == currentNumber && ! _isResultOfAddition[pathToMoveTo.row][pathToMoveTo.section])
    {
        self.hasMoved = YES;
        
        [self.emptyTiles addObject:index];
        _tiles[index.row][index.section] = 0;
        _tiles[pathToMoveTo.row][pathToMoveTo.section] = _tiles[pathToMoveTo.row][pathToMoveTo.section]*2;
        _isResultOfAddition[pathToMoveTo.row][pathToMoveTo.section] = YES;
        //TODO: game should be inited with this!
        if(_tiles[pathToMoveTo.row][pathToMoveTo.section] == kGameEndNumber) [self.delegate gameOverWithReason:WIN];
        
        self.score +=  _tiles[pathToMoveTo.row][pathToMoveTo.section];
        
        [self.delegate moveNumberViewWithAdditionFromIndex:index toIndex:pathToMoveTo];
    }
    
    //otherwise move to the last empty tile
    else
    {
        if(_tiles[pathToMoveTo.row][pathToMoveTo.section] != 0)
            pathToMoveTo = [pathToMoveTo subtractIndexPath:increment];
        
        if(![pathToMoveTo isEqual: index])
        {

            self.hasMoved = YES;
             [self.emptyTiles addObject:index];
            _tiles[index.row][index.section] = 0;
             [self.emptyTiles removeObject:pathToMoveTo];
            _tiles[pathToMoveTo.row][pathToMoveTo.section] = currentNumber;
            //[self.delegate updateTileAtIndex:pathToMoveTo];
            [self.delegate moveNumberViewAtIndex:index toIndex:pathToMoveTo];
        }
    }
    
}

-(BOOL) indexPathIsOutsideBounds
:(NSIndexPath*) path
{
    if(path.row < 0) return YES;
    if(path.row > self.size.y - 1) return YES;
    if(path.section < 0) return YES;
    if(path.section > self.size.x - 1) return YES;
    return NO;
}


-(void)printTiles
{
    for(int row = 0; row < self.size.x; row++)
    {
        for(int col = 0; col < self.size.y; col++)
        {
            printf("%d ",_tiles[row][col]);
            
        }
        printf("\n");
    }
    printf("---------- \n");

}


-(void) generateNewTile
{
    int indexOfRandomTile = arc4random_uniform([self.emptyTiles count]);
    NSIndexPath* pathToGenerateAt = (NSIndexPath*)([self.emptyTiles objectAtIndex:indexOfRandomTile]);
    _tiles[pathToGenerateAt.row][pathToGenerateAt.section] = [IOTCGame generateRandomNumber];
    [self.delegate newTileSpawnAtIndex:pathToGenerateAt];
    [self.delegate updateTileAtIndex:pathToGenerateAt];
    [self.emptyTiles removeObjectAtIndex:indexOfRandomTile];
    if([self.emptyTiles count] == 0)
    {
        if(![self existAdjacentAddibleTiles]){
            [self.delegate gameOverWithReason:LOOSE];
        }
    }
}

+ (NSInteger) generateRandomNumber {
    NSInteger percentage = arc4random() % 100;
    if (percentage > 10) {
        return 2;
    } else {
        return 4;
    }
}

-(BOOL)existAdjacentAddibleTiles
{
    for(int row = 0; row < self.size.y - 1; row++)
    {
        for(int col = 0; col < self.size.x - 1; col++)
        {
            int numberToCheck = _tiles[row][col];
            if( numberToCheck == _tiles[row+1][col] ) return YES;
            if( numberToCheck == _tiles[row][col+1]) return YES;
        }
    }
    
    for(int col = 0; col < self.size.x - 1; col++)
    {
        int numberToCheck = _tiles[(int)self.size.y - 1][col];
        if( numberToCheck == _tiles[(int)self.size.y - 1][col+1]) return YES;
    }
    
    for(int row = 0; row < self.size.y - 1; row++)
    {
            int numberToCheck = _tiles[row][(int)self.size.x - 1];
            if( numberToCheck == _tiles[row+1][(int)self.size.x - 1] ) return YES;
 
    }

    return NO;
}

-(void)setScore:(unsigned long)score
{
    _score = score;
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NewScore" object:[NSNumber numberWithLongLong:self.score]];
}



@end
